package com.bailian.spring.servelt.demo;

import com.bailian.spring.servelt.demo.dao.DemoDao;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author gaoxu
 * date: Created on 2021/10/28 下午2:57
 */
public class BeanFactory {

    private static Properties properties;

    // 使用静态代码块初始化properties，加载factord.properties文件
    static {
        properties = new Properties();
        try {
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
        } catch (IOException e) {
            // BeanFactory类的静态初始化代码都出现了问题,那就直接抛出异常信息
            e.printStackTrace();
            throw new ExceptionInInitializerError("BeanFactory initialize error,cause:" + e.getMessage());
        }
    }

    /**
     * 问题3:getDemoDaoByProperties 如果是频繁调用,那么会生成多个实例对象
     * <p>
     * 优化4:引入缓存解决
     */
    private static Map<String, Object> beanMap = new HashMap<>();

    /**
     * 优化4:
     * 使用缓存处理,避免频繁创建对象
     *
     * @param beanName
     * @return
     */
    public static Object getBeanByPropertiesAndMap(String beanName) {
        // 依赖双重加锁保证beanMap中确实没有beanName这个实例对象
        if (!beanMap.containsKey(beanName)) {
            synchronized (BeanFactory.class) {
                if (!beanMap.containsKey(beanName)) {
                    try {
                        // 反射创建对象
                        Class<?> beanClazz = Class.forName(properties.getProperty(beanName));
                        Object bean = beanClazz.newInstance();
                        // 放入缓存中,然后返回
                        beanMap.put(beanName, bean);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
                    } catch (IllegalAccessException | InstantiationException e) {
                        throw new RuntimeException("[" + beanName + "] instantiation error!", e);
                    }
                }
            }
        }
        return beanMap.get(beanName);
    }

    /**
     * 问题2:但是每次是使用mysql还是oracle实现的Dao还是依赖上面的进行硬编码写死,切换还是需要更改应代码,
     * 解决:具体读取那个配置从外部文件中读取
     * <p>
     * 优化3:根据外部配置文件,然后根据传入进来的值进行实例创建;
     *
     * @param beanName
     * @return
     */
    public static Object getDemoDaoByProperties(String beanName) {
        try {
            Class<?> beanClazz = Class.forName(properties.getProperty(beanName));
            return beanClazz.newInstance();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException("[" + beanName + "] instantiation error!", e);
        }
    }

    /**
     * 优化1: 利用静态工厂,将不同的实现类维护在静态工厂里
     *
     * @return
     */
    public static DemoDao getDemoDao() {

        //return new DemoDaoImpl();
        //return new DemoOracleDao();

        /**
         * 问题1:依赖类文件只能使用new这种方式创建对象
         *
         * 优化2:
         * 再次优化,利用反射来减少代码强耦合,
         * 反射！反射可以声明一个类的全限定名，来获取它的字节码描述，这样也能构造对象！
         * 再次启动的时候,不会因为没有DemoDao的实现类而编译报错,在运行阶段报错;
         */
        try {
            return (DemoDao) Class.forName("com.bailian.spring.servelt.demo.dao.impl.DemoDaoImpl").newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("DemoDao instantiation error, cause: " + e.getMessage());
        }
    }
}
